package cn.blu10ph.trustshare.service.impl;

import cn.blu10ph.trustshare.bean.msg.BaseMsg;
import cn.blu10ph.trustshare.bean.msg.TrustMsg;
import cn.blu10ph.trustshare.bean.node.TrustNode;
import cn.blu10ph.trustshare.config.ServiceConfig;
import cn.blu10ph.trustshare.constant.Constant;
import cn.blu10ph.trustshare.core.PGPManager;
import cn.blu10ph.trustshare.core.RoutingTable;
import cn.blu10ph.trustshare.service.DHTMsgService;
import cn.blu10ph.trustshare.service.NoticeService;
import cn.blu10ph.trustshare.util.ConvertUtil;
import cn.blu10ph.trustshare.util.HashAndSignUtil;
import cn.blu10ph.trustshare.util.PgpUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.openpgp.PGPException;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;

/**
 * <p> DHTMsgBaseService </p >
 *
 * @author cxx
 * @date 2024/4/6 22:22
 */
@Slf4j
public abstract class DHTMsgBaseService<T> implements DHTMsgService<T> {

    @Resource
    ServiceConfig config;

    @Resource
    RoutingTable routingTable;

    @Resource
    PGPManager pgpManager;

    @Resource
    NoticeService noticeService;

    protected static ChannelFuture channel;

    public static void setChannel(ChannelFuture channel) {
        DHTMsgBaseService.channel = channel;
    }

    protected BaseMsg getBaseMsg(String direction, String to){
        BaseMsg msgObj = new BaseMsg();
        msgObj.setType(getType());
        msgObj.setDirection(direction);
        msgObj.setFrom(routingTable.getNodeId());
        msgObj.setTo(to);
        return msgObj;
    }

    @Override
    public T getParams(String params) throws JsonProcessingException {
        Class<T> clazz = getParamsType();
        return ConvertUtil.str2Obj(params, clazz);
    }

    protected abstract Class<T> getParamsType();

    @Override
    public void sendQuery(TrustNode node, T params) throws Exception {
        sendQuery(node.getNodeId(), node.getHost(), node.getPort(), params);
    }

    public void sendQuery(String nodeId, String hostname, int port, T params) throws Exception {
        BaseMsg msgObj = getBaseMsg(Constant.MSG_DIRECTION_QUERY, nodeId);
        try {
            msgObj.setParams(ConvertUtil.obj2Str(params));
        } catch (JsonProcessingException ex) {
            ex.printStackTrace();
        }
        send(hostname, port, msgObj);
    }

    public TrustMsg getTrustMsgByBase(BaseMsg msgObj) throws IOException, NoSuchAlgorithmException, PGPException {
        TrustMsg trustMsg = new TrustMsg(msgObj);
        trustMsg.setTime(System.currentTimeMillis());
        trustMsg.setNonce(HashAndSignUtil.getNonce(trustMsg.getTime()));

        String msgHash = HashAndSignUtil.getMsgHash(trustMsg);
        String sign = PgpUtil.createSignature(msgHash, PGPManager.getSecretKey(), config.getPassword());
        trustMsg.setSign(sign);
        return trustMsg;
    }

    public void send(String hostname, int port, BaseMsg msgObj) {
        try {
            TrustMsg trustMsg = getTrustMsgByBase(msgObj);
            String msg = ConvertUtil.obj2Str(trustMsg);
            send(hostname, port, msg);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void send(String hostname, int port, String msg) {
        send(new InetSocketAddress(hostname, port), msg);
    }

    public void send(InetSocketAddress recipient, String msg) {
        send(new DatagramPacket(ConvertUtil.str2ByteBuf(msg), recipient));
    }

    public void send(DatagramPacket packet) {
        log.info("send [{}:{}],msg:{}",
                packet.recipient().getHostString(),
                packet.recipient().getPort(),
                new String(packet.content().array(), StandardCharsets.UTF_8)
        );
        channel.channel().writeAndFlush(packet);
    }

    public void response(TrustNode node, ChannelHandlerContext ctx, InetSocketAddress recipient, BaseMsg msgObj) {
        try {
            TrustMsg trustMsg = getTrustMsgByBase(msgObj);
            String msg = ConvertUtil.obj2Str(trustMsg);
            response(node, ctx, recipient, msg);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void response(TrustNode node, ChannelHandlerContext ctx, InetSocketAddress recipient, String msg) {
        log.info("response node[{}] channel id:{};msg:{}", node.getNodeId(), ctx.channel().id(), msg);
        ctx.channel().writeAndFlush(
                new DatagramPacket(ConvertUtil.str2ByteBuf(msg), recipient)
        );
    }

}
